
/*
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * SPDX-License-Identifier: GPL-3.0+
 * License-Filename: LICENSE
 *
 */

#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "splay-tree.h"
#include "main.h"
#include "parse.h"
#include "mem.h"
#include "color.h"
#include "options.h"
#include "folding.h"
#include "nes.h"
#include "uniqstring.h"
#include "uniqnode.h"
#include "nes.h"
#include "folding.h"

/* helper struct for splay edge cache */
struct ectys {
	char *fromname;
	char *toname;
};

/* in generated tys.flex.c */
extern void yyset_in(FILE * f);
extern int yylex(void);
extern void yyset_debug(int bdebug);

/* last seen string */
char *laststring = NULL;

/* last seen identifier */
char *lastid = NULL;

/* last seen number */
char *lastnum = NULL;

/* last seen html string */
char *lasthtml = NULL;

/* last seen single char */
char *lastchar = NULL;

/* data format of graph to parse */
int parsedformat = 0;

/* parsed token from yylex (); */
static int token = 0;

/* last parse error string */
char *parser_error_string = NULL;

/* saved error codes */
static int errorlineno = 0;
static char *errorstring = NULL;
static int els_lineno = 1;
static char els_buf[1024];	/* line buffer */

static int getparsererrorlineno(void);
static char *getparsererrorstring(void);
static int parse1(char *fname, FILE * fstream);
static void parsederror(void);
static void parsedempty(void);
static int parse2(struct usubg *subg);
static int parseels(char *fname, FILE * fstream);
static int elslex_lineno(void);
static void parseels_clear(void);
static int is_a_node(struct usubg *subg);
static int is_a_edge(struct usubg *subg);
static int is_a_settings(struct usubg *subg);
static int is_a_subgraph(struct usubg *subg);

/* clear all parser data */
void parse_clear(void)
{

	/* saved error codes */
	if (errorlineno == 0) {
		errorlineno = getparsererrorlineno();
	}

	if (errorstring == NULL) {
		errorstring = getparsererrorstring();
		if (errorstring == NULL) {
			errorstring = (char *)"unknown error";
		}
	}

	parsetys_clear();
	parsedot_clear();
	parseels_clear();

	return;
}

/* name of file to parse */
static char *fn = (char *)0;

/* parse filename switching parsers+lexers if needed */
void parse(char *filename)
{
	int status = 0;
	FILE *f = NULL;

	/* clear errors */
	errorlineno = 0;
	errorstring = NULL;
	parser_error_string = NULL;

	/* check filename */
	if (filename == NULL) {
		/* shouldnothappen */
		return;
	}

	/* or "" */
	if (strlen(filename) == 0) {
		/* shouldnothappen */
		return;
	}

	/* strdup because uniqstring can get cleared at parse error */
	fn = strdup(filename);

	if (option_parsedebug) {
		printf("%s(): parsing file %s\n", __FUNCTION__, fn);
		fflush(stdout);
	}

	f = fopen(fn, "rb");

	if (f == NULL) {
		fprintf(stderr, "%s(): cannot open file %s for reading\n", __FUNCTION__, fn);
		fflush(stderr);
		free(fn);
		return;
	}

	rewind(f);

	/* set stream to read graph */
	yyset_in(f);

	/* set lexer debug 0=off, 1=on */
	yyset_debug(option_parsedebug);

	/* parse the graph */
	status = parse1(fn, f);

	/* */
	if (status) {
		/* when there is parse error then data will be deleted and node "parse-error" is created */
	}

	/* clear up the lexers */
	parse_clear();

	fclose(f);

	if (option_parsedebug) {
		printf("%s(): status %d from parsing file %s\n", __FUNCTION__, status, fn);
		fflush(stdout);
	}

	free(fn);
	fn = NULL;

	return;
}

/* clear all tys parser data */
void parsetys_clear(void)
{
	/* in tys.l */
	tyslexreset();
	return;
}

/* */
static int getparsererrorlineno(void)
{
	int ret = 0;
	if (parsedformat == 0) {
		ret = tyslex_lineno();
	} else if (parsedformat == 1) {
		ret = dotlex_lineno();
	} else if (parsedformat == 2) {
		ret = elslex_lineno();
	} else {
		ret = 0;
	}
	return (ret);
}

/* */
static char *getparsererrorstring(void)
{
	char *ret = NULL;
	if (parsedformat == 0) {
		ret = parser_error_string;
	} else if (parsedformat == 1) {
		ret = parser_error_string;
	} else if (parsedformat == 2) {
		ret = parser_error_string;
	} else {
		ret = parser_error_string;
	}
	if (ret == NULL) {
		ret = (char *)"unknown error";
	}
	/* cannot use uniqstring() here because that gets reset
	 * at parse error and the error string must be static
	 * and not-malloced string to avoid mem leakages.
	 */
	return (ret);
}

/* */
static int parse1(char *fname, FILE * fstream)
{
	int ret = 0;

	/* default to tys format */
	parsedformat = 0;

	/* no errors */
	parser_error_string = NULL;

	token = yylex();

	update_statusline("Parsing... Wait...");

	if (option_parsedebug) {
		printf("%s(): token=%d\n", __FUNCTION__, token);
		fflush(stdout);
	}

	if (token == '}') {
		parser_error_string = "expected 'void', 'strict', 'graph' or 'digraph' keyword";
		prepare4newdata();
		parsederror();
		return (0);
	}

	if (option_parsedebug) {
		printf("%s(): token=%d\n", __FUNCTION__, token);
		fflush(stdout);
	}

	if (option_parsedebug) {
		printf("%s(): token=%d\n", __FUNCTION__, token);
		fflush(stdout);
	}

	/* dot keywords as first token */
	if ((token == TYS_UTF8BOM) || (token == TYS_STRICT) || (token == TYS_GRAPH) || (token == TYS_DIGRAPH)) {
		tyslexreset();

		/* graphviz file may start with a utf-8 begin-of-message code */
		parsedformat = 1;

		if (option_parsedebug || 0) {
			printf("%s(): dot parse started for file %s\n", __FUNCTION__, fname);
		}

		ret = parsedot(fname, fstream);

		if (option_parsedebug || 0) {
			printf("%s(): status %d from parsing dot file %s\n", __FUNCTION__, ret, fname);
			fflush(stdout);
		}

		/* create node with error message string */
		if (ret) {
			prepare4newdata();
			parsederror();
			return (0);
		}

		/* if no node data parsed create "empty graph" message */
		if (splay_tree_has_data(sp_parsednl) == 0) {
			/* no data parsed */
			prepare4newdata();
			parsedempty();
			return (0);
		}

		/* parsed oke and there is node data */

		return (0);
	}

	/* if first char is a number, then guess the els graph data format */
	if (token >= '0' && token <= '9') {

		if (option_parsedebug) {
			printf("%s(): first char is a number guessing els filetype\n", __FUNCTION__);
		}

		tyslexreset();

		/* assume els file */
		parsedformat = 2;

		rewind(fstream);

		ret = parseels(fname, fstream);

		if (ret == 0) {
			if (parser_error_string == NULL) {
				parser_error_string = "expected numbers\nin els format";
			}
			prepare4newdata();
			parsederror();
			return (0);
		}

		if (splay_tree_has_data(sp_parsednl) == 0) {
			/* no data parsed */
			prepare4newdata();
			/* create node with "empty graph" text */
			parsedempty();
		} else {	/* oke */
		}

		return (0);
	}

	if (token != TYS_VOID) {
		parser_error_string = "expected 'void' keyword";
		prepare4newdata();
		parsederror();
		return (0);
	} else {		/* oke */
	}

	token = yylex();
	if (token != TYS_GRAPH) {
		parser_error_string = "expected 'graph' keyword";
		prepare4newdata();
		parsederror();
		return (0);
	}

	token = yylex();
	if (token != '(') {
		parser_error_string = "expected '(' at 'void graph(void)";
		prepare4newdata();
		parsederror();
		return (0);
	}

	token = yylex();
	if (token != TYS_VOID) {
		parser_error_string = "expected 'void' keyword";
		prepare4newdata();
		parsederror();
		return (0);
	}

	token = yylex();
	if (token != ')') {
		parser_error_string = "expected ')' at 'void graph(void)";
		prepare4newdata();
		parsederror();
		return (0);
	}

	token = yylex();
	if (token != '{') {
		parser_error_string = "expected '{' after 'void graph(void)";
		prepare4newdata();
		parsederror();
		return (0);
	}

	/* parsing own format */
	parsedformat = 0;

	/* start parse at toplevel root graph */
	ret = parse2(NULL);

	if (ret == 0) {
		if (parser_error_string == NULL) {
			parser_error_string = "expected node, edge, subgraph\nor settings in body of graph";
		}
		prepare4newdata();
		parsederror();
		return (0);
	}

	if (splay_tree_has_data(sp_parsednl) == 0) {
		/* no data parsed */
		prepare4newdata();
		/* create node with "empty graph" text */
		parsedempty();
	} else {		/* oke */
	}

	/* ready with tys data parsing */
	return (0);
}

/* node with parse error message in root graph */
static void parsederror(void)
{
	char buf[256];
	char *s = NULL;
	char *s2 = NULL;
	struct unode *un = NULL;
	int lineno = 0;
	char *estring = NULL;
	lineno = errorlineno;
	memset(buf, 0, 256);
	if (errorstring == NULL) {
		estring = "unknown error";
	} else {
		estring = errorstring;
	}
	snprintf(buf, (256 - 1), "parsing error happened\nnear line %d:\n%s\n(or use commandline option -P to get debug output)\n",
		 lineno, estring);
	s = uniqstring(buf);
	s2 = uniqstring((char *)"parsederror");
	un = unode_new((char *)s2);
	un->label = s;
	if (option_parsedebug) {
		printf("%s(): error text is `%s'\n", __FUNCTION__, s);
		fflush(stdout);
	}
	/* on stderr to use with scripted tests */
	fprintf(stderr, "file `%s' error at line %d: %s\n", fn, lineno, estring);
	fflush(stderr);
	un->indegree = 0;
	un->outdegree = 0;
	/* put node in root graph */
	un->rootedon = NULL;
	/* edgelabel node to draw different */
	un->bitflags.edgelabel = 1;
	un->bitflags.parseerror = 1;
	un->bitflags.defbynode = 1;
	uniqnode_add(un->name, un);
	parsednl_add(un);
	if (option_dryrun) {
		printf("%s(): `%s'\n", __FUNCTION__, un->label);
		fflush(stdout);
	}
	return;
}

/* no data parsed */
static void parsedempty(void)
{
	struct unode *un = NULL;
	char *s = NULL;
	char *s2 = NULL;
	s = uniqstring((char *)"emptygraph");
	un = unode_new(s);
	s2 = uniqstring((char *)"empty graph");
	un->label = s2;
	/* node is in root graph */
	un->rootedon = NULL;
	un->indegree = 0;
	un->outdegree = 0;
	/* edgelabel node to draw different */
	un->bitflags.edgelabel = 1;
	un->bitflags.parseerror = 1;
	un->bitflags.defbynode = 1;
	uniqnode_add(un->name, un);
	parsednl_add(un);
	if (option_dryrun) {
		printf("%s(): %s\n", __FUNCTION__, un->label);
		fflush(stdout);
	}
	return;
}

/* parse body of graph or subgraph */
static int parse2(struct usubg *subg)
{

	for (;;) {
		token = yylex();
		if (token == 0) {
			return (0);
		} else if (token == '}') {
			return (1);
		} else if (is_a_node(subg)) {
		} else if (is_a_edge(subg)) {
		} else if (is_a_settings(subg)) {
		} else if (is_a_subgraph(subg)) {
		} else {
			return (0);
		}
	}
	return (1);
}

/* node definition to parse */
static int is_a_node(struct usubg *subg)
{
	int color = 0;
	struct unode *un = NULL;
	if (token != TYS_NODE) {
		return (0);
	}
	token = yylex();
	if (token != '(') {
		parser_error_string = "expected '(' at node(\"nodename\")";
		return (0);
	}
	token = yylex();
	if (token != TYS_STRING) {
		parser_error_string = "expected \"nodename\" at node(\"nodename\")";
		return (0);
	}
	token = yylex();
	if (token != ')') {
		parser_error_string = "expected ')' at node(\"nodename\")";
		return (0);
	}
	un = uniqnode(laststring);
	if (un == (struct unode *)0) {
		un = unode_new(laststring);
		un->label = un->name;
		un->rootedon = subg;
		/* node is defined by node statement */
		un->bitflags.defbynode = 1;
		un->fontsize = NULL;
		uniqnode_add(un->name, un);
		parsednl_add(un);
	} else {
		/* node is multiple times defined and use the first node definition.
		 * or node is only earlier created by edge statement.
		 */
		/* check if only defined by edge */
		if (un->bitflags.defbynode == 0 && un->bitflags.defbyedge == 1) {
			/* check if node is in different subgraph */
			if (un->rootedon != subg) {
				/* put node in current subgraph */
				un->rootedon = subg;
				/* node is defined by node statement */
				un->bitflags.defbynode = 1;
				/* node is relocated during parse */
				un->bitflags.reloc = 1;
			}
		}
	}
	/* check for end of node statement */
	token = yylex();
	if (token == ';') {
		return (1);
	}
	if (token != '{') {
		parser_error_string = "expected '{' at start of node attribute";
		return (0);
	}
	/* now {} with the node options */
	for (;;) {
		token = yylex();
		if (token == 0) {
			parser_error_string = "end of file at node(\"nodename\") { }";
		} else if (token == '}') {
			break;
		} else if (token == TYS_LABEL) {
			token = yylex();
			if (token != '=') {
				parser_error_string = "expected '=' at node attribute label=\"string\";";
				return (0);
			}
			token = yylex();
			if (token != TYS_STRING) {
				parser_error_string = "expected \"string\" at node attribute label=\"string\";";
				return (0);
			}
			/* if set force to use the node name as node label in drawing */
			if (option_nodenames) {
				un->label = un->name;
			} else {
				if (strlen(laststring) == 0) {
					un->label = NULL;
				} else {
					un->label = laststring;
				}
			}
			token = yylex();
			if (token != ';') {
				parser_error_string = "expected ';' at node attribute label=\"string\";";
				return (0);
			}
		} else if (token == TYS_URL) {
			token = yylex();
			if (token != '=') {
				parser_error_string = "expected '=' at node attribute url=\"url-string\";";
				return (0);
			}
			token = yylex();
			if (token != TYS_STRING) {
				parser_error_string = "expected \"url\" at node attribute url=\"url-string\";";
				return (0);
			}
			/* check for "" */
			if (strlen(laststring) == 0) {
				un->url = NULL;
			} else {
				un->url = laststring;
			}
			token = yylex();
			if (token != ';') {
				parser_error_string = "expected ';' at node attribute url=\"url-string\";";
				return (0);
			}
		} else if (token == TYS_FONTNAME) {
			token = yylex();
			if (token != '=') {
				parser_error_string = "expected '=' at node attribute fontname=\"string\";";
				return (0);
			}
			token = yylex();
			if (token != TYS_STRING) {
				parser_error_string = "expected \"fontname\" at node attribute fontname=\"string\";";
				return (0);
			}
			if (strlen(laststring) == 0) {
				un->fontname = NULL;
			} else {
				un->fontname = laststring;
			}
			token = yylex();
			if (token != ';') {
				parser_error_string = "expected ';' at node attribute fontname=\"string\";";
				return (0);
			}
		}

		else if (token == TYS_TEXTSLANT) {
			token = yylex();
			if (token != '=') {
				parser_error_string = "expected '=' at node attribute textslant=\"string\";";
				return (0);
			}
			token = yylex();
			if (token != TYS_STRING) {
				parser_error_string = "expected \"italic\" or \"oblique\" at node attribute textslant=\"string\";";
				return (0);
			}
			if (strcmp(laststring, "italic") == 0) {
				un->bitflags.textitalic = 1;
			} else if (strcmp(laststring, "oblique") == 0) {
				un->bitflags.textoblique = 1;
			} else {
				/* slant is normal */
				un->bitflags.textitalic = 0;
				un->bitflags.textoblique = 0;
			}
			token = yylex();
			if (token != ';') {
				parser_error_string = "expected ';' at node attribute textslant=\"string\";";
				return (0);
			}
		} else if (token == TYS_TEXTWEIGHT) {
			token = yylex();
			if (token != '=') {
				parser_error_string = "expected '=' at node attribute textweight=\"string\";";
				return (0);
			}
			token = yylex();
			if (token != TYS_STRING) {
				parser_error_string = "expected \"bold\" at node attribute textweight=\"string\";";
				return (0);
			}
			if (strcmp(laststring, "bold") == 0) {
				/* text is in weight bold */
				un->bitflags.textbold = 1;
			} else {
				/* weight is normal */
				un->bitflags.textbold = 0;
			}
			token = yylex();
			if (token != ';') {
				parser_error_string = "expected '=' at node attribute textweight=\"string\";";
				return (0);
			}
		} else if (token == TYS_FONTSIZE) {
			token = yylex();
			if (token != '=') {
				parser_error_string = "expected '=' at node attribute fonsize=\"number\";";
				return (0);
			}
			token = yylex();
			if (token != TYS_STRING) {
				parser_error_string = "expected \"number\" at node attribute fonsize=\"number\";";
				return (0);
			}
			/* check for reasonable font size */
			if (atoi(laststring) < 1 || atoi(laststring) > 100) {
				/* use gtk default fontsize if out of range */
				un->fontsize = NULL;
			} else {
				un->fontsize = laststring;
			}
			token = yylex();
			if (token != ';') {
				parser_error_string = "expected ';' at node attribute fonsize=\"number\";";
				return (0);
			}
		} else if (token == TYS_COLOR) {
			token = yylex();
			if (token != '=') {
				parser_error_string = "expected '=' at node attribute color=\"colorname\";";
				return (0);
			}
			token = yylex();
			if (token != TYS_STRING) {
				parser_error_string = "expected \"colorname\" at node attribute color=\"colorname\";";
				return (0);
			}
			color = colorcode(laststring);
			if (color == -1) {
				/* white background */
				un->fillcolor = 0x00ffffff;
			} else {
				un->fillcolor = color;
			}
			token = yylex();
			if (token != ';') {
				parser_error_string = "expected ';' at node attribute color=\"colorname\";";
				return (0);
			}
		} else if (token == TYS_TEXTCOLOR) {
			token = yylex();
			if (token != '=') {
				parser_error_string = "expected '=' at node attribute textcolor=\"colorname\";";
				return (0);
			}
			token = yylex();
			if (token != TYS_STRING) {
				parser_error_string = "expected \"colorname\" at node attribute textcolor=\"colorname\";";
				return (0);
			}
			color = colorcode(laststring);
			if (color == -1) {
				/* black text */
				un->textcolor = 0x00;
			} else {
				un->textcolor = color;
			}
			token = yylex();
			if (token != ';') {
				parser_error_string = "expected ';' at node attribute textcolor=\"colorname\";";
				return (0);
			}
		} else if (token == TYS_SHAPE) {
			token = yylex();
			if (token != '=') {
				parser_error_string = "expected '=' at node attribute shape=\"nodeshape\";";
				return (0);
			}
			token = yylex();
			if (token != TYS_STRING) {
				parser_error_string =
				    "expected \"nodeshape\" box, rbox, circle or ellips at node attribute shape=\"nodeshape\";";
				return (0);
			}
			if (strcmp(laststring, "box") == 0) {
				un->bitflags.shape = NSHAPE_BOX;
			} else if (strcmp(laststring, "rect") == 0) {
				un->bitflags.shape = NSHAPE_BOX;
			} else if (strcmp(laststring, "rectangle") == 0) {
				un->bitflags.shape = NSHAPE_BOX;
			} else if (strcmp(laststring, "rbox") == 0) {
				un->bitflags.shape = NSHAPE_RBOX;
			} else if (strcmp(laststring, "dbox") == 0) {
				un->bitflags.shape = NSHAPE_DBOX;
			} else if (strcmp(laststring, "circle") == 0) {
				un->bitflags.shape = NSHAPE_CIRCLE;
			} else if (strcmp(laststring, "card") == 0) {
				un->bitflags.shape = NSHAPE_CARD;
			} else if (strcmp(laststring, "ellips") == 0) {
				un->bitflags.shape = NSHAPE_ELLIPS;
			} else if (strcmp(laststring, "ellipse") == 0) {
				un->bitflags.shape = NSHAPE_ELLIPS;
			} else if (strcmp(laststring, "cloud") == 0) {
				un->bitflags.shape = NSHAPE_CLOUD;
			} else if (strcmp(laststring, "dbox") == 0) {
				/* box shape with doubled border */
				un->bitflags.shape = NSHAPE_DBOX;
			} else if (strcmp(laststring, "diamond") == 0) {
				un->bitflags.shape = NSHAPE_DIAMOND;
			} else if (strcmp(laststring, "mdiamond") == 0) {
				un->bitflags.shape = NSHAPE_MDIAMOND;
			} else if (strcmp(laststring, "hexagon") == 0) {
				un->bitflags.shape = NSHAPE_HEXAGON;
			} else if (strcmp(laststring, "hex") == 0) {
				un->bitflags.shape = NSHAPE_HEXAGON;
			} else if (strcmp(laststring, "pentagon") == 0) {
				un->bitflags.shape = NSHAPE_PENTAGON;
			} else if (strcmp(laststring, "folder") == 0) {
				un->bitflags.shape = NSHAPE_FOLDER;
			} else if (strcmp(laststring, "cube") == 0) {
				un->bitflags.shape = NSHAPE_CUBE;
			} else if (strcmp(laststring, "sbox") == 0) {
				un->bitflags.shape = NSHAPE_SBOX;
			} else if (strcmp(laststring, "rcloud") == 0) {
				un->bitflags.shape = NSHAPE_RCLOUD;
			} else if (strcmp(laststring, "chevron") == 0) {
				un->bitflags.shape = NSHAPE_CHEVRON;
			} else if (strcmp(laststring, "cylinder") == 0) {
				un->bitflags.shape = NSHAPE_CYLINDER;
			} else if (strcmp(laststring, "dialog") == 0) {
				un->bitflags.shape = NSHAPE_DIALOG;
			} else if (strcmp(laststring, "maximize") == 0) {
				un->bitflags.shape = NSHAPE_MAXIMIZE;
			} else if (strcmp(laststring, "page") == 0) {
				un->bitflags.shape = NSHAPE_PAGE;
			} else if (strcmp(laststring, "plaque") == 0) {
				un->bitflags.shape = NSHAPE_PLAQUE;
			} else if (strcmp(laststring, "prep") == 0) {
				un->bitflags.shape = NSHAPE_PREP;
			} else if (strcmp(laststring, "punched") == 0) {
				un->bitflags.shape = NSHAPE_PUNCHED;
			} else if (strcmp(laststring, "shield") == 0) {
				un->bitflags.shape = NSHAPE_SHIELD;
			} else if (strcmp(laststring, "septagon") == 0) {
				un->bitflags.shape = NSHAPE_SEPTAGON;
			} else if (strcmp(laststring, "stored") == 0) {
				un->bitflags.shape = NSHAPE_STORED;
			} else if (strcmp(laststring, "trapezoid") == 0) {
				un->bitflags.shape = NSHAPE_TRAPEZOID;
			} else if (strcmp(laststring, "vscroll") == 0) {
				un->bitflags.shape = NSHAPE_VSCROLL;
			} else if (strcmp(laststring, "triangle") == 0) {
				un->bitflags.shape = NSHAPE_TRIANGLE;
			} else if (strcmp(laststring, "itriangle") == 0) {
				un->bitflags.shape = NSHAPE_ITRIANGLE;
			} else {
				/* use precompiled default */
				un->bitflags.shape = NSHAPE_DEFAULT;
			}
			token = yylex();
			if (token != ';') {
				parser_error_string = "expected ';' at node attribute shape=\"nodeshape\";";
				return (0);
			}
		} else {
			/* not node statement */
			return (0);
		}
	}
	/* oke parsed node statement */
	return (1);
}

/* free function for splay edge cache value data */
static void is_a_edge_free_value(splay_tree_value ptr)
{
	if (ptr) {
		myfree((void *)ptr, __FUNCTION__, __LINE__);
	}
	return;
}

/* edge statement */
static int is_a_edge(struct usubg *subg)
{
	char *elabel = NULL;
	int color = 0;
	int thickness = 0;
	int textcolor = 0;
	int textitalic = 0;
	int textoblique = 0;
	int textbold = 0;
	char *fontsize = NULL;
	char *fontname = NULL;
	int style = ESTYLE_NORMAL;
	int arrows = 0;
	int botharrows = 0;
	int multarrows = 0;
	char *fromname = NULL;
	char *toname = NULL;
	struct unode *fnn = NULL;
	struct unode *tn = NULL;
	struct uedge *ue = NULL;
	splay_tree ect = NULL;	/* edge cache */
	splay_tree_node spn = NULL;
	int nect = 0;		/* number of entries in splay */
	struct ectys *ecptr = NULL;
	int i = 0;

	if (token != TYS_EDGE) {
		/* not a edge statement */
		return (0);
	}

	token = yylex();

	if (token != '(') {
		parser_error_string = "expected '(' at 'edge(\"from-name\",\"to-name\")'";
		return (0);
	}

	token = yylex();

	if (token != TYS_STRING) {
		parser_error_string = "expected \"from-name\" at 'edge(\"from-name\",\"to-name\")'";
		return (0);
	}

	fromname = laststring;

	token = yylex();

	if (token != ',') {
		parser_error_string = "expected ',' at 'edge(\"from-name\",\"to-name\")'";
		return (0);
	}

	token = yylex();

	if (token != TYS_STRING) {
		parser_error_string = "expected \"to-name\" at 'edge(\"from-name\",\"to-name\")'";
		return (0);
	}

	toname = laststring;

	/* create the edge cache */
	ect = splay_tree_new(splay_tree_compare_ints, NULL, is_a_edge_free_value);

	/* number of entries in splay */
	nect = 0;

	/* create value for first edge */
	ecptr = mymalloc(sizeof(struct ectys), __FUNCTION__, __LINE__);
	ecptr->fromname = fromname;
	ecptr->toname = toname;

	/* add in splay tree */
	splay_tree_insert(ect, (splay_tree_key) nect, (splay_tree_value) ecptr);

	/* number of entries in splay */
	nect = nect + 1;

	/* check if more edges follow */
	token = yylex();

	if (token == ',') {
		/* expect more edges */
		for (;;) {
			fromname = toname;
			token = yylex();
			if (token != TYS_STRING) {
				parser_error_string = "expected \"to-name\" at 'edge(\"from-name\",\"to-name\")'";
				ect = splay_tree_delete(ect);
				return (0);
			}
			toname = laststring;
			/* create value for next edge */
			ecptr = mymalloc(sizeof(struct ectys), __FUNCTION__, __LINE__);
			ecptr->fromname = fromname;
			ecptr->toname = toname;
			/* add in splay tree */
			splay_tree_insert(ect, (splay_tree_key) nect, (splay_tree_value) ecptr);
			/* number of entries in splay */
			nect = nect + 1;
			/* check if more edges follow */
			token = yylex();
			if (token == ',') {
				continue;
			}
			/* check if at end of edges */
			if (token == ')') {
				break;
			} else {
				ect = splay_tree_delete(ect);
				parser_error_string = "expected ')' at 'edge(\"from-name\",\"to-name\")'";
				return (0);
			}
		}
	}
	/* if only one edge expect the close paren */
	if (nect == 1) {
		if (token != ')') {
			ect = splay_tree_delete(ect);
			parser_error_string = "expected ')' at 'edge(\"from-name\",\"to-name\")'";
			return (0);
		}
	}
	token = yylex();
	/* check if end-of-edge or edge options */
	if (token == ';') {
		elabel = NULL;
		color = 0;
		style = ESTYLE_NORMAL;
		/* scan the cached edges */
		for (i = 0; i < nect; i++) {
			/* get splay data */
			spn = splay_tree_lookup(ect, (splay_tree_key) i);
			if (spn == NULL) {
				ect = splay_tree_delete(ect);
				parser_error_string = "unexpected spn at 'edge(\"from-name\",\"to-name\")'";
				return (0);
			}
			ecptr = (struct ectys *)spn->value;
			if (ecptr == NULL) {
				ect = splay_tree_delete(ect);
				parser_error_string = "unexpected ecptr at 'edge(\"from-name\",\"to-name\")'";
				return (0);
			}
			/* set the from/to name names */
			fromname = (char *)ecptr->fromname;
			toname = (char *)ecptr->toname;
			/* regular edge */
			fnn = uniqnode(fromname);
			if (fnn == (struct unode *)0) {
				/* node is not yet defined */
				fnn = unode_new(fromname);
				fnn->label = fnn->name;
				fnn->rootedon = subg;
				/* node is defined by edge statement */
				fnn->bitflags.defbyedge = 1;
				uniqnode_add(fnn->name, fnn);
				parsednl_add(fnn);
			} else {
				/* node is defined with a node statement */
			}
			tn = uniqnode(toname);
			if (tn == (struct unode *)0) {
				/* node is not yet defined */
				tn = unode_new(toname);
				tn->label = tn->name;
				tn->rootedon = subg;
				/* node is defined by edge statement */
				tn->bitflags.defbyedge = 1;
				uniqnode_add(tn->name, tn);
				parsednl_add(tn);
			} else {
				/* node is defined with a node statement */
			}

			/* fresh edge */
			ue = uedge_new(fnn, tn);
			ue->bitflags.thickness = 0;
			/* black edge line */
			ue->color = 0x00;
			/* solid edge line */
			ue->bitflags.style = ESTYLE_NORMAL;
			ue->rootedon = subg;

			/* add edge to edgelist */
			parsedel_add(ue);
		}

		ect = splay_tree_delete(ect);
		return (1);
	}
	/* check for chained edge a->b->c */
	if (token == ',') {
		/* insert first edge in tree */
	}
	/* edge with options */
	if (token != '{') {
		parser_error_string = "expected '{' after 'edge(\"from-name\",\"to-name\")'";
		ect = splay_tree_delete(ect);
		return (0);
	}
	/* options for the edge */
	elabel = NULL;
	thickness = 0;
	color = 0;
	fontsize = NULL;
	textcolor = 0;
	textitalic = 0;
	textoblique = 0;
	textbold = 0;
	fontname = NULL;
	style = ESTYLE_NORMAL;
	arrows = 1;		/* draw arrows at edge */
	botharrows = 0;
	multarrows = 0;
	for (;;) {
		token = yylex();
		if (token == 0) {
			parser_error_string = "end of file at edge attribute";
			ect = splay_tree_delete(ect);
			return (0);
		} else if (token == '}') {
			break;
		} else if (token == TYS_COLOR) {
			token = yylex();
			if (token != '=') {
				parser_error_string = "expected '=' at edge 'color=\"colorname\");' attribute";
				ect = splay_tree_delete(ect);
				return (0);
			}
			token = yylex();
			if (token != TYS_STRING) {
				parser_error_string = "expected \"colorname\" at edge 'color=\"colorname\");' attribute";
				ect = splay_tree_delete(ect);
				return (0);
			}
			color = colorcode(laststring);
			if (color == -1) {
				/* black edge line */
				color = 0;
			}
			token = yylex();
			if (token != ';') {
				parser_error_string = "expected ';' at edge 'color=\"colorname\");' attribute";
				ect = splay_tree_delete(ect);
				return (0);
			}
		} else if (token == TYS_TEXTCOLOR) {
			token = yylex();
			if (token != '=') {
				parser_error_string = "expected '=' at edge 'textcolor=\"colorname\");' attribute";
				ect = splay_tree_delete(ect);
				return (0);
			}
			token = yylex();
			if (token != TYS_STRING) {
				parser_error_string = "expected \"colorname\" at edge 'textcolor=\"colorname\");' attribute";
				ect = splay_tree_delete(ect);
				return (0);
			}
			textcolor = colorcode(laststring);
			if (color == -1) {
				/* black label text color */
				textcolor = 0;
			}
			token = yylex();
			if (token != ';') {
				parser_error_string = "expected ';' at edge 'textcolor=\"colorname\");' attribute";
				ect = splay_tree_delete(ect);
				return (0);
			}
		} else if (token == TYS_ARROWS) {
			token = yylex();
			if (token != '=') {
				parser_error_string = "expected '=' at edge 'arrows=\"yes/no/both/multiple\");' attribute";
				ect = splay_tree_delete(ect);
				return (0);
			}
			token = yylex();
			if (token != TYS_STRING) {
				parser_error_string =
				    "expected \"yes\", \"no\", \"both\" or \"multiple\" at edge 'arrows=\"yes/no/both/multiple\");' attribute";
				ect = splay_tree_delete(ect);
				return (0);
			}
			/* check the arg */
			if (strcmp(laststring, "yes") == 0) {
				arrows = 1;
			} else if (strcmp(laststring, "no") == 0) {
				arrows = 0;
			} else if (strcmp(laststring, "both") == 0) {
				botharrows = 1;
			} else if (strcmp(laststring, "multiple") == 0) {
				multarrows = 1;
			} else {
				/* unknown arg */
				arrows = 1;
				botharrows = 0;
				multarrows = 0;
			}
			token = yylex();
			if (token != ';') {
				parser_error_string = "expected ';' at edge 'arrows=\"yes/no/both/multiple\");' attribute";
				ect = splay_tree_delete(ect);
				return (0);
			}
		} else if (token == TYS_LABEL) {
			token = yylex();
			if (token != '=') {
				parser_error_string = "expected '=' at edge 'label=\"labelname\");' attribute";
				ect = splay_tree_delete(ect);
				return (0);
			}
			token = yylex();
			if (token != TYS_STRING) {
				parser_error_string = "expected \"labelname\" at edge 'label=\"labelname\");' attribute";
				ect = splay_tree_delete(ect);
				return (0);
			}
			elabel = laststring;
			if (strlen(laststring) == 0) {
				elabel = NULL;
			}
			token = yylex();
			if (token != ';') {
				parser_error_string = "expected ';' at edge 'label=\"labelname\");' attribute";
				ect = splay_tree_delete(ect);
				return (0);
			}
		} else if (token == TYS_FONTNAME) {
			token = yylex();
			if (token != '=') {
				parser_error_string = "expected '=' at edge 'fontname=\"fontname\");' attribute";
				ect = splay_tree_delete(ect);
				return (0);
			}
			token = yylex();
			if (token != TYS_STRING) {
				parser_error_string = "expected \"fontname\" at edge 'fontname=\"fontname\");' attribute";
				ect = splay_tree_delete(ect);
				return (0);
			}
			if (strlen(laststring) == 0) {
				fontname = NULL;
			} else {
				fontname = laststring;
			}
			token = yylex();
			if (token != ';') {
				parser_error_string = "expected ';' at edge 'fontname=\"fontname\");' attribute";
				ect = splay_tree_delete(ect);
				return (0);
			}
		} else if (token == TYS_TEXTSLANT) {
			token = yylex();
			if (token != '=') {
				parser_error_string = "expected '=' at edge 'textslant=\"slant\");' attribute";
				ect = splay_tree_delete(ect);
				return (0);
			}
			token = yylex();
			if (token != TYS_STRING) {
				parser_error_string = "expected \"slant\" at edge 'textslant=\"slant\");' attribute";
				ect = splay_tree_delete(ect);
				return (0);
			}
			if (strcmp(laststring, "italic") == 0) {
				textitalic = 1;
			} else if (strcmp(laststring, "oblique") == 0) {
				textoblique = 1;
			} else {
				/* slant is normal */
				textitalic = 0;
				textoblique = 0;
			}
			token = yylex();
			if (token != ';') {
				parser_error_string = "expected ';' at edge 'textslant=\"slant\");' attribute";
				ect = splay_tree_delete(ect);
				return (0);
			}
		} else if (token == TYS_TEXTWEIGHT) {
			token = yylex();
			if (token != '=') {
				parser_error_string = "expected '=' at edge 'textweight=\"weight\");' attribute";
				ect = splay_tree_delete(ect);
				return (0);
			}
			token = yylex();
			if (token != TYS_STRING) {
				parser_error_string = "expected \"slant\" at edge 'textweight=\"weight\");' attribute";
				ect = splay_tree_delete(ect);
				return (0);
			}
			if (strcmp(laststring, "bold") == 0) {
				/* bold text weight */
				textbold = 1;
			} else {
				/* weight is normal */
				textbold = 0;
			}
			token = yylex();
			if (token != ';') {
				parser_error_string = "expected ';' at edge 'textweight=\"weight\");' attribute";
				ect = splay_tree_delete(ect);
				return (0);
			}
		} else if (token == TYS_FONTSIZE) {
			token = yylex();
			if (token != '=') {
				parser_error_string = "expected '=' at edge 'fontsize=\"number\");' attribute";
				ect = splay_tree_delete(ect);
				return (0);
			}
			token = yylex();
			if (token != TYS_STRING) {
				parser_error_string = "expected \"number\" at edge 'fontsize=\"number\");' attribute";
				ect = splay_tree_delete(ect);
				return (0);
			}
			/* check for reasonable font size */
			if (atoi(laststring) < 1 || atoi(laststring) > 100) {
				/* use gtk default, 0 is value not set */
				fontsize = NULL;
			} else {
				fontsize = laststring;
			}
			token = yylex();
			if (token != ';') {
				parser_error_string = "expected ';' at edge 'fontsize=\"number\");' attribute";
				ect = splay_tree_delete(ect);
				return (0);
			}
		} else if (token == TYS_STYLE) {
			token = yylex();
			if (token != '=') {
				parser_error_string = "expected '=' at edge 'style=\"stylename\");' attribute";
				ect = splay_tree_delete(ect);
				return (0);
			}
			token = yylex();
			if (token != TYS_STRING) {
				parser_error_string = "expected \"stylename\" at edge 'style=\"stylename\");' attribute";
				ect = splay_tree_delete(ect);
				return (0);
			}
			if (strcmp(laststring, "normal") == 0) {
				style = ESTYLE_NORMAL;
			} else if (strcmp(laststring, "solid") == 0) {
				style = ESTYLE_SOLID;
			} else if (strcmp(laststring, "dotted") == 0) {
				style = ESTYLE_DOTTED;
			} else if (strcmp(laststring, "dashed") == 0) {
				style = ESTYLE_DASHED;
			} else if (strcmp(laststring, "invis") == 0) {
				style = ESTYLE_INVIS;
			} else {
				style = ESTYLE_NORMAL;
			}
			token = yylex();
			if (token != ';') {
				parser_error_string = "expected ';' at edge 'style=\"stylename\");' attribute";
				ect = splay_tree_delete(ect);
				return (0);
			}
		} else if (token == TYS_THICKNESS) {
			token = yylex();
			if (token != '=') {
				parser_error_string = "expected '=' at edge 'thickness=\"number\");' attribute";
				ect = splay_tree_delete(ect);
				return (0);
			}
			token = yylex();
			if (token != TYS_STRING) {
				parser_error_string = "expected \"number\" at edge 'thickness=\"number\");' attribute";
				ect = splay_tree_delete(ect);
				return (0);
			}
			/* check for reasonable thickness */
			thickness = atoi(laststring);
			if (thickness < 0 || thickness > 7) {
				/* use default, 0 is value not set */
				thickness = 0;
			}
			token = yylex();
			if (token != ';') {
				parser_error_string = "expected ';' at edge 'thickness=\"number\");' attribute";
				ect = splay_tree_delete(ect);
				return (0);
			}
		}

		else {
			parser_error_string = "unknown edge attribute";
			ect = splay_tree_delete(ect);
			return (0);
		}
	}
	/* add the edges cached in splay */
	for (i = 0; i < nect; i++) {
		/* get splay data */
		spn = splay_tree_lookup(ect, (splay_tree_key) i);
		if (spn == NULL) {
			ect = splay_tree_delete(ect);
			parser_error_string = "unexpected spn at 'edge(\"from-name\",\"to-name\")'";
			return (0);
		}
		ecptr = (struct ectys *)spn->value;
		if (ecptr == NULL) {
			ect = splay_tree_delete(ect);
			parser_error_string = "unexpected ecptr at 'edge(\"from-name\",\"to-name\")'";
			return (0);
		}
		/* set the from/to name names */
		fromname = (char *)ecptr->fromname;
		toname = (char *)ecptr->toname;
		/* regular edge with or without edge label */
		fnn = uniqnode(fromname);
		if (fnn == (struct unode *)0) {
			/* node is not yet defined */
			fnn = unode_new(fromname);
			fnn->label = fnn->name;
			fnn->rootedon = subg;
			/* node is defined by edge statement */
			fnn->bitflags.defbyedge = 1;
			uniqnode_add(fnn->name, fnn);
			parsednl_add(fnn);
		} else {
			/* node is defined with a node statement */
		}
		tn = uniqnode(toname);
		if (tn == (struct unode *)0) {
			/* node is not yet defined */
			tn = unode_new(toname);
			tn->label = tn->name;
			tn->rootedon = subg;
			/* node is defined by edge statement */
			tn->bitflags.defbyedge = 1;
			uniqnode_add(tn->name, tn);
			parsednl_add(tn);
		} else {
			/* node is defined with a node statement */
		}
		if (elabel) {
			/* if edge label is "" (empty) zero it */
			if (strlen(elabel) == 0) {
				elabel = NULL;
			}
		}
		/* check for edge label */
		if (elabel) {
			/* edge with edge label is always in drawing */
			ue = NULL;
		}

		/* create the edge */
		ue = uedge_new(fnn, tn);

		/* if edge label set the text attributes */
		if (elabel) {
			ue->label = elabel;
			ue->bitflags.thickness = thickness;
			ue->textcolor = textcolor;	/* black text */
			ue->fontsize = fontsize;	/* gtk default */
			ue->fontname = fontname;	/* default font */
			if (textbold) {
				ue->bitflags.textbold = 1;
			}
			if (textitalic) {
				ue->bitflags.textitalic = 1;
			}
			if (textoblique) {
				ue->bitflags.textoblique = 1;
			}
		}
		ue->color = color;
		ue->bitflags.style = style;
		/* configure edge arrow bits */
		if (arrows == 1) {
			ue->bitflags.arrows = 1;
			if (botharrows) {
				ue->bitflags.botharrows = 1;
			}
			if (multarrows) {
				ue->bitflags.multarrows = 1;
			}
		} else {
			ue->bitflags.arrows = 0;
			ue->bitflags.botharrows = 0;
			ue->bitflags.multarrows = 0;
		}
		ue->rootedon = subg;

		/* add edge to edgelist */
		parsedel_add(ue);
	}

	ect = splay_tree_delete(ect);

	return (1);
}

/* graph settings */
static int is_a_settings(struct usubg *subg)
{
	int color = 0;
	if (token != TYS_SETTINGS) {
		return (0);
	}
	token = yylex();
	if (token != '(') {
		parser_error_string = "expected '(' at 'settings(void)'";
		return (0);
	}
	token = yylex();
	if (token != TYS_VOID) {
		parser_error_string = "expected 'void' at 'settings(void)'";
		return (0);
	}
	token = yylex();
	if (token != ')') {
		parser_error_string = "expected ')' at 'settings(void)'";
		return (0);
	}
	token = yylex();
	if (token == ';') {
		return (1);
	}
	if (token != '{') {
		parser_error_string = "expected '{' at 'settings(void)'";
		return (0);
	}
	/* default white background color */
	color = 0x00ffffff;
	/* now {} with the settings options */
	for (;;) {
		token = yylex();
		if (token == 0) {
			parser_error_string = "end of file at 'settings(void){}'";
			return (0);
		} else if (token == '}') {
			break;
		} else if (token == TYS_COLOR) {
			token = yylex();
			if (token != '=') {
				parser_error_string = "expected '=' at 'color=\"colorname\";'";
				return (0);
			}
			token = yylex();
			if (token != TYS_STRING) {
				parser_error_string = "expected \"color name\" at 'color=\"colorname\";'";
				return (0);
			}
			color = colorcode(laststring);
			if (color == -1) {
				/* unknown color choose default white */
				if (subg == NULL) {
					/* white background color of the root graph */
					bgcr = 0xff;
					bgcg = 0xff;
					bgcb = 0xff;
				} else {
					/* white background color of the summary node */
					subg->summaryn->fillcolor = 0x00ffffff;
				}
			} else {
				/* specified background color of the root graph */
				if (subg == NULL) {
					bgcr = (color & 0x00ff0000) >> 16;
					bgcg = (color & 0x0000ff00) >> 8;
					bgcb = (color & 0x000000ff);
				} else {
					/* background color of the summary node */
					subg->summaryn->fillcolor = color;
				}
			}
			token = yylex();
			if (token != ';') {
				parser_error_string = "expected ';' at 'color=\"colorname\";'";
				return (0);
			}
		} else if (token == TYS_LABEL) {
			token = yylex();
			if (token != '=') {
				parser_error_string = "expected '=' at 'label=\"string\";'";
				return (0);
			}
			token = yylex();
			if (token != TYS_STRING) {
				parser_error_string = "expected \"string\" at 'label=\"string\";'";
				return (0);
			}
			if (subg == NULL) {
				/* root graph does not have a label name */
			} else {
				if (option_nodenames) {
					subg->summaryn->label = subg->summaryn->name;
				} else {
					/* if it is "" (empty) */
					if (strlen(laststring) == 0) {
						subg->summaryn->label = subg->summaryn->name;
					} else {
						subg->summaryn->label = laststring;
					}
				}
			}
			token = yylex();
			if (token != ';') {
				parser_error_string = "expected ';' at 'label=\"string\";'";
				return (0);
			}
		} else if (token == TYS_FOLDED) {
			token = yylex();
			if (token != '=') {
				parser_error_string = "expected '=' at 'folded=\"yes/no\";'";
				return (0);
			}
			token = yylex();
			if (token != TYS_STRING) {
				parser_error_string = "expected \"yes\" or \"no\" at 'folded=\"yes/no\";'";
				return (0);
			}
			if (subg == NULL) {
				/* root graph cannot be folded */
				/* folding setting for all subgraphs at once */
				if (strcmp(laststring, "yes") == 0) {
					settings_init_folded = 2;
				} else if (strcmp(laststring, "no") == 0) {
					settings_init_folded = 3;
				} else {
					settings_init_folded = 0;
				}
			} else {
				if (strcmp(laststring, "yes") == 0) {
					subg->bitflags.folded = 1;
				} else if (strcmp(laststring, "no") == 0) {
					subg->bitflags.folded = 0;
				} else {
					parser_error_string = "expected \"yes\" or \"no\" at 'folded=\"yes/no\";'";
					return (0);
				}
				/* commandline option overrides the graph data option */
				if (option_init_unfolded) {
					subg->bitflags.folded = 0;
				}
				if (option_init_folded) {
					subg->bitflags.folded = 1;
				}
			}
			token = yylex();
			if (token != ';') {
				parser_error_string = "expected ';' at 'folded=\"yes/no\";'";
				return (0);
			}
		} else {
			return (0);
		}
	}
	return (1);
}

/* parsing a subgraph */
static int is_a_subgraph(struct usubg *subg)
{
	struct usubg *newsg = NULL;
	char *sgname = NULL;
	int ret = 0;
	if (token != TYS_SUBGRAPH) {
		return (0);
	}
	token = yylex();
	if (token != '(') {
		parser_error_string = "expected '(' at 'subgraph(\"subgraphname\")'";
		return (0);
	}
	token = yylex();
	if (token != TYS_STRING) {
		parser_error_string = "expected \"string\" at 'subgraph(\"subgraphname\")'";
		return (0);
	}
	sgname = laststring;
	token = yylex();
	if (token != ')') {
		parser_error_string = "expected ')' at 'subgraph(\"subgraphname\")'";
		return (0);
	}
	token = yylex();
	if (token == ';') {
		/* empty subgraph without nodes, edges or options */
		if (sgname) {
			newsg = usubg_new(sgname, subg);
			/* initial unfolded subgraph */
			newsg->bitflags.folded = 0;
			if (option_init_unfolded) {
				newsg->bitflags.folded = 0;
			}
			if (option_init_folded) {
				newsg->bitflags.folded = 1;
			}
			/* add subgraph to list of subgraphs */
			parsedsgl_add(newsg);
			/* configure summarynode */
			/* summary node is part of new subgraph */
			newsg->summaryn->rootedon = newsg;
			/* white background color */
			newsg->summaryn->fillcolor = 0x00ffffff;
			/* this is a subgraph summary node */
			newsg->summaryn->bitflags.sumnode = 1;
			/* add summary node to list of summary nodes */
			summarynl_add(newsg->summaryn);
			/* set which is the starting subgraph in root graph */
			if (subg == NULL) {
				/* started in root graph */
				newsg->rootsubgraph = newsg;
			} else {
				if (subg->rootedon == NULL) {
					/* started in root graph */
					newsg->rootsubgraph = newsg;
				} else {
					/* is a subsubgraph */
					newsg->rootsubgraph = subg->rootsubgraph;
				}
			}
		}
		return (1);
	}
	if (token != '{') {
		parser_error_string = "expected '{' at 'subgraph(\"subgraphname\")'";
		return (0);
	}
	if (sgname) {
		newsg = usubg_new(sgname, subg);
		/* initially unfolded subgraph */
		newsg->bitflags.folded = 0;
		if (option_init_unfolded) {
			newsg->bitflags.folded = 0;
		}
		if (option_init_folded) {
			newsg->bitflags.folded = 1;
		}
		/* add subgraph to list of subgraphs */
		parsedsgl_add(newsg);
		/* configure summary node */
		/* summary node is part of new subgraph */
		newsg->summaryn->rootedon = newsg;
		/* white background color */
		newsg->summaryn->fillcolor = 0x00ffffff;
		/* this is a subgraph summary node */
		newsg->summaryn->bitflags.sumnode = 1;
		/* add summary node to list of summary nodes */
		summarynl_add(newsg->summaryn);
		/* set which is the starting subgraph in root graph */
		if (subg == NULL) {
			/* started in root graph */
			newsg->rootsubgraph = newsg;
			newsg->rootedon = NULL;
		} else {
			/* is a subsubgraph */
			newsg->rootsubgraph = subg->rootsubgraph;
		}
	} else {
		parser_error_string = "expected new subgraph";
		return (0);
	}
	/* recurse into parsing a new sub graph */
	ret = parse2(newsg);

	/* status 1 if oke */
	return (ret);
}

/* clear lexer at end-of-file */
static void parseels_clear(void)
{
	els_lineno = 1;
	memset(els_buf, 0, 1024);
	return;
}

/* get lineno for els parsing */
static int elslex_lineno(void)
{
	return (els_lineno);
}

/* assume els graph data format
 *
 * The first line contains:
 * n m k
 * where n = number of nodes,
 * m = number of edges in the planar subgraph,
 * k = number of deleted edges
 * Then come the edges as simple edge list.
 * Each line contains a pair of node indices
 * (node indices are in the range 0..n-1).
 * The first m lines specify the edges in the subgraph.
 * The next k lines specify the deleted edges.
 * Example:
 * 3 2 1
 * 0 1
 * 1 2
 * 2 3
 *
 * Specifies the planar subgraph with nodes v0, v1, v2
 * and edges { (v0,v1), (v1,v2) }
 * and the removed edges are F = { (v2,v3) }
 */

static int parseels(char *fname, FILE * fstream)
{
	int i = 0;
	int n = 0;
	int ncount = 0;
	int ecount = 0;
	int dcount = 0;
	int fnnumber = 0;
	int tnnumber = 0;
	char fnname[32];
	char tnname[32];
	char *fromname = NULL;
	char *toname = NULL;
	struct unode *fnn = (struct unode *)0;
	struct unode *tnn = (struct unode *)0;
	struct uedge *ue = (struct uedge *)0;

	els_lineno = 1;
	memset(els_buf, 0, 1024);

	if (option_parsedebug) {
		printf("%s(): parsing file \"%s\" as a els file\n", __FUNCTION__, fname);
	}

	/* get first line with 3 numbers */
	if (fgets(els_buf, 1024, fstream) == NULL) {
		if (option_parsedebug) {
			printf("%s(): error reading first line\n", __FUNCTION__);
		}
		parser_error_string = "error reading first line els file";
		return (0);
	}

	/* read the first line with numbers, node-count, edge-count, deleted-edge count */
	n = sscanf(els_buf, "%d %d %d", &ncount, &ecount, &dcount);

	if ((n != 3) || (n == EOF)) {
		if (option_parsedebug) {
			printf("%s(): no 3 numbers on first line\n", __FUNCTION__);
		}
		parser_error_string = "no 3 numbers in first line els file";
		return (0);
	}

	if (option_parsedebug) {
		printf("%s(): node-count=%d edge-count=%d deleted-edge-count=%d\n", __FUNCTION__, ncount, ecount, dcount);
	}

	/* read the edge lines */
	for (i = 0; i < ecount; i++) {

		els_lineno = els_lineno + 1;
		memset(els_buf, 0, 1024);

		/* read edge info line */
		if (fgets(els_buf, 1024, fstream) == NULL) {
			if (option_parsedebug) {
				printf("%s(): error reading els line %d\n", __FUNCTION__, els_lineno);
			}
			parser_error_string = "error reading line in els file";
			return (0);
		}

		/* read edge data */
		n = sscanf(els_buf, "%d %d", &fnnumber, &tnnumber);

		if ((n != 2) || (n == EOF)) {
			if (option_parsedebug) {
				printf("%s(): no 2 node numbers on edge line %d\n", __FUNCTION__, els_lineno);
			}
			parser_error_string = "no 2 node numbers in edge line els file";
			return (0);
		}

		if (option_parsedebug) {
			printf("%s(): found edge %d->%d\n", __FUNCTION__, fnnumber, tnnumber);
		}

		/* convert the numbers as strings */
		memset(fnname, 0, 32);
		memset(tnname, 0, 32);
		(void)snprintf(fnname, (32 - 1), "%d", fnnumber);
		(void)snprintf(tnname, (32 - 1), "%d", tnnumber);

		/* set the from/to name names */
		fromname = (char *)uniqstring(fnname);
		toname = (char *)uniqstring(tnname);

		/* check if exists as node */
		fnn = uniqnode(fromname);

		if (fnn == (struct unode *)0) {
			/* node is not yet defined */
			fnn = unode_new(fromname);
			fnn->label = fnn->name;
			fnn->rootedon = NULL;
			/* node is defined by edge statement */
			fnn->bitflags.defbyedge = 1;
			uniqnode_add(fnn->name, fnn);
			parsednl_add(fnn);
		} else {
			/* node is defined earlier */
		}

		tnn = uniqnode(toname);

		if (tnn == (struct unode *)0) {
			/* node is not yet defined */
			tnn = unode_new(toname);
			tnn->label = tnn->name;
			tnn->rootedon = NULL;
			/* node is defined by edge statement */
			tnn->bitflags.defbyedge = 1;
			uniqnode_add(tnn->name, tnn);
			parsednl_add(tnn);
		} else {
			/* node is defined earlier */
		}

		/* create the edge */
		ue = uedge_new(fnn, tnn);

		/* edge rooted in root graph */
		ue->rootedon = NULL;

		/* add edge to edgelist */
		parsedel_add(ue);

		if (option_parsedebug || 0) {
			printf("%s(): created edge number=[e%d] %s->%s\n", __FUNCTION__, ue->number, fnn->name, tnn->name);
		}

		/* to the next edge */
	}

	/* status 1 means oke */
	return (1);
}

/* end */
